package Hxw3DEngine.asset.parsers
{
	
	import Hxw3DEngine.asset.parsers.ParserBase;
	import Hxw3DEngine.utils.Stage3dUtils;
	
	import flash.net.URLRequest;

	public class A3DObjParser extends ParserBase
	{
		private var _textData:String;
		private var _startedParsing:Boolean;
		private var _charIndex:uint;
		private var _oldIndex:uint;
		private var _stringLength:uint;
		private var _currentObject:ObjectGroup;
		private var _currentGroup:Group;
		private var _currentMaterialGroup:MaterialGroup;
		private var _objects:Vector.<ObjectGroup>;
		private var _materialIDs:Vector.<String>;
		private var _lastMtlID:String;
		private var _objectIndex:uint;
		private var _realIndices:Array;
		private var _vertexIndex:uint;
		private var _scale:Number = 1;
		private var _mtlLib:Boolean;
		private var _mtlLibLoaded:Boolean = true;
		private var _activeMaterialID:String = "";
		
		
		private var _vertices:Vector.<Vertex>;
		private var _uvs:Vector.<UV>;
		private var _vertexNormals:Vector.<Vertex>;
		
		
		
		override public function proceedParsing():Boolean
		{
			var line:String;
			var creturn:String = String.fromCharCode(10);
			var trunk:Array;
			
			if (!startedParsing) {
				_textData = Stage3dUtils.toString(targetData);
				_textData = _textData.replace(/\\[\r\n]+\s*/gm, ' ');
			}
			
			if (_textData.indexOf(creturn) == -1)
				creturn = String.fromCharCode(13);
			
			if (!startedParsing) {
				startedParsing = true;
				
				_vertices = new Vector.<Vertex>;
				_uvs = new Vector.<UV>;
				_vertexNormals = new Vector.<Vertex>;
				_realIndices = [];
				
				_materialIDs = new Vector.<String>();
				_stringLength = _textData.length;
				_charIndex = _textData.indexOf(creturn, 0);
				_oldIndex = 0;
				_objects = new Vector.<ObjectGroup>();
				_objectIndex = 0;
			}
			
			while (_charIndex < _stringLength && hasTime()) {
				_charIndex = _textData.indexOf(creturn, _oldIndex);
				
				if (_charIndex == -1)
					_charIndex = _stringLength;
				
				line = _textData.substring(_oldIndex, _charIndex);
				line = line.split('\r').join("");
				line = line.replace("  ", " ");
				trunk = line.split(" ");
				_oldIndex = _charIndex + 1;
				parseLine(trunk);
				trace("parser ........(" + _charIndex + "/" + _stringLength + ")");
				return false;
			}
			
			if (_charIndex >= _stringLength) {
				/*
				if (_mtlLib && !_mtlLibLoaded)
					return false;*/
				translate();
				return true;
			}
			
			return false;
		}
		
		/**
		 * Parses a single line in the OBJ file.
		 */
		private function parseLine(trunk:Array):void
		{
			switch (trunk[0]) {
				case "mtllib":
					_mtlLib = true;
					_mtlLibLoaded = false;
					//loadMtl(trunk[1]);
					break;
				case "g":
					createGroup(trunk);
					break;
				case "o":
					createObject(trunk);
					break;
				case "usemtl":
					if (_mtlLib) {
						if (!trunk[1])
							trunk[1] = "def000";
						_materialIDs.push(trunk[1]);
						_activeMaterialID = trunk[1];
						if (_currentGroup)
							_currentGroup.materialID = _activeMaterialID;
					}
					break;
				case "v":
					parseVertex(trunk);
					break;
				case "vt":
					parseUV(trunk);
					break;
				case "vn":
					parseVertexNormal(trunk);
					break;
				case "f":
					parseFace(trunk);
			}
		}
		
		private function translate():void
		{
			for (var objIndex:int = 0; objIndex < _objects.length; ++objIndex) {
				var groups:Vector.<Group> = _objects[objIndex].groups;
				var numGroups:uint = groups.length;
				var materialGroups:Vector.<MaterialGroup>;
				var numMaterialGroups:uint;
				var m:uint;
				var sm:uint;
				for (var g:uint = 0; g < numGroups; ++g) {
					materialGroups = groups[g].materialGroups;
					numMaterialGroups = materialGroups.length;
					for (m = 0; m < numMaterialGroups; ++m)
						translateMaterialGroup(materialGroups[m]);
				}
			}
		}
		
		private function translateMaterialGroup(materialGroup:MaterialGroup):void
		{
			var faces:Vector.<FaceData> = materialGroup.faces;
			var face:FaceData;
			var numFaces:uint = faces.length;
			var numVerts:uint;
			
			var vertices:Vector.<Number> = new Vector.<Number>();
			var uvs:Vector.<Number> = new Vector.<Number>();
			var normals:Vector.<Number> = new Vector.<Number>();
			var indices:Vector.<uint> = new Vector.<uint>();
			
			var _realIndices:Array = [];
			var _vertexIndex:int = 0;
			
			var j:uint;
			for (var i:uint = 0; i < numFaces; ++i) {
				face = faces[i];
				numVerts = face.indexIds.length - 1;
				for (j = 1; j < numVerts; ++j) {
					translateVertexData(face, j, vertices, uvs, indices, normals);
					translateVertexData(face, 0, vertices, uvs, indices, normals);
					translateVertexData(face, j + 1, vertices, uvs, indices, normals);
				}
			}
			if (vertices.length > 0) {
				//subs = GeomUtil.fromVectors(vertices, indices, uvs, normals, null, null, null);
			}
		}
		
		private function translateVertexData(face:FaceData, vertexIndex:int, vertices:Vector.<Number>, uvs:Vector.<Number>, indices:Vector.<uint>, normals:Vector.<Number>):void
		{
			var index:uint;
			var vertex:Vertex;
			var vertexNormal:Vertex;
			var uv:UV;
			
			if (!_realIndices[face.indexIds[vertexIndex]]) {
				index = _vertexIndex;
				_realIndices[face.indexIds[vertexIndex]] = ++_vertexIndex;
				vertex = _vertices[face.vertexIndices[vertexIndex] - 1];
				vertices.push(vertex.x*_scale, vertex.y*_scale, vertex.z*_scale);
				
				if (face.normalIndices.length > 0) {
					vertexNormal = _vertexNormals[face.normalIndices[vertexIndex] - 1];
					normals.push(vertexNormal.x, vertexNormal.y, vertexNormal.z);
				}
				
				if (face.uvIndices.length > 0) {
					
					try {
						uv = _uvs[face.uvIndices[vertexIndex] - 1];
						uvs.push(uv.u, uv.v);
						
					} catch (e:Error) {
						
						switch (vertexIndex) {
							case 0:
								uvs.push(0, 1);
								break;
							case 1:
								uvs.push(.5, 0);
								break;
							case 2:
								uvs.push(1, 1);
						}
					}
					
				}
				
			} else
				index = _realIndices[face.indexIds[vertexIndex]] - 1;
			
			indices.push(index);
		}
		
		/**
		 * Creates a new object group.
		 * @param trunk The data block containing the object tag and its parameters
		 */
		private function createObject(trunk:Array):void
		{
			_currentGroup = null;
			_currentMaterialGroup = null;
			_objects.push(_currentObject = new ObjectGroup());
			
			if (trunk)
				_currentObject.name = trunk[1];
		}
		
		/**
		 * Creates a new group.
		 * @param trunk The data block containing the group tag and its parameters
		 */
		private function createGroup(trunk:Array):void
		{
			if (!_currentObject)
				createObject(null);
			_currentGroup = new Group();
			
			_currentGroup.materialID = _activeMaterialID;
			
			if (trunk)
				_currentGroup.name = trunk[1];
			_currentObject.groups.push(_currentGroup);
			
			createMaterialGroup(null);
		}
		
		/**
		 * Creates a new material group.
		 * @param trunk The data block containing the material tag and its parameters
		 */
		private function createMaterialGroup(trunk:Array):void
		{
			_currentMaterialGroup = new MaterialGroup();
			if (trunk)
				_currentMaterialGroup.url = trunk[1];
			_currentGroup.materialGroups.push(_currentMaterialGroup);
		}
		
		/**
		 * Reads the next vertex coordinates.
		 * @param trunk The data block containing the vertex tag and its parameters
		 */
		private function parseVertex(trunk:Array):void
		{
			//for the very rare cases of other delimiters/charcodes seen in some obj files
			if (trunk.length > 4) {
				var nTrunk:Array = [];
				var val:Number;
				for (var i:uint = 1; i < trunk.length; ++i) {
					val = parseFloat(trunk[i]);
					if (!isNaN(val))
						nTrunk.push(val);
				}
				_vertices.push(new Vertex(nTrunk[0], nTrunk[1], -nTrunk[2]));
			} else
				_vertices.push(new Vertex(parseFloat(trunk[1]), parseFloat(trunk[2]), -parseFloat(trunk[3])));
			
		}
		
		/**
		 * Reads the next uv coordinates.
		 * @param trunk The data block containing the uv tag and its parameters
		 */
		private function parseUV(trunk:Array):void
		{
			if (trunk.length > 3) {
				var nTrunk:Array = [];
				var val:Number;
				for (var i:uint = 1; i < trunk.length; ++i) {
					val = parseFloat(trunk[i]);
					if (!isNaN(val))
						nTrunk.push(val);
				}
				_uvs.push(new UV(nTrunk[0], 1 - nTrunk[1]));
				
			} else
				_uvs.push(new UV(parseFloat(trunk[1]), 1 - parseFloat(trunk[2])));
			
		}
		
		/**
		 * Reads the next vertex normal coordinates.
		 * @param trunk The data block containing the vertex normal tag and its parameters
		 */
		private function parseVertexNormal(trunk:Array):void
		{
			if (trunk.length > 4) {
				var nTrunk:Array = [];
				var val:Number;
				for (var i:uint = 1; i < trunk.length; ++i) {
					val = parseFloat(trunk[i]);
					if (!isNaN(val))
						nTrunk.push(val);
				}
				_vertexNormals.push(new Vertex(nTrunk[0], nTrunk[1], -nTrunk[2]));
				
			} else
				_vertexNormals.push(new Vertex(parseFloat(trunk[1]), parseFloat(trunk[2]), -parseFloat(trunk[3])));
		}
		
		/**
		 * Reads the next face's indices.
		 * @param trunk The data block containing the face tag and its parameters
		 */
		private function parseFace(trunk:Array):void
		{
			var len:uint = trunk.length;
			var face:FaceData = new FaceData();
			
			if (!_currentGroup)
				createGroup(null);
			
			var indices:Array;
			for (var i:uint = 1; i < len; ++i) {
				if (trunk[i] == "")
					continue;
				indices = trunk[i].split("/");
				face.vertexIndices.push(parseIndex(parseInt(indices[0]), _vertices.length));
				if (indices[1] && String(indices[1]).length > 0)
					face.uvIndices.push(parseIndex(parseInt(indices[1]), _uvs.length));
				if (indices[2] && String(indices[2]).length > 0)
					face.normalIndices.push(parseIndex(parseInt(indices[2]), _vertexNormals.length));
				face.indexIds.push(trunk[i]);
			}
			
			_currentMaterialGroup.faces.push(face);
		}
		
		/**
		 * This is a hack around negative face coords
		 */
		private function parseIndex(index:int, length:uint):int
		{
			if (index < 0)
				return index + length + 1;
			else
				return index;
		}
		
		private function parseMtl(data:String):void
		{
			var materialDefinitions:Array = data.split('newmtl');
			var lines:Array;
			var trunk:Array;
			var j:uint;
			
			var useSpecular:Boolean;
			var useColor:Boolean;
			var diffuseColor:uint;
			var ambientColor:uint;
			var specularColor:uint;
			var specular:Number;
			var alpha:Number;
			var mapkd:String;
			
			for (var i:uint = 0; i < materialDefinitions.length; ++i) {
				
				lines = (materialDefinitions[i].split('\r') as Array).join("").split('\n');
				
				if (lines.length == 1)
					lines = materialDefinitions[i].split(String.fromCharCode(13));
				
				diffuseColor = ambientColor = specularColor = 0xFFFFFF;
				specular = 0;
				useSpecular = false;
				useColor = false;
				alpha = 1;
				mapkd = "";
				
				for (j = 0; j < lines.length; ++j) {
					lines[j] = lines[j].replace(/\s+$/, "");
					
					if (lines[j].substring(0, 1) != "#" && (j == 0 || lines[j] != "")) {
						trunk = lines[j].split(" ");
						
						if (String(trunk[0]).charCodeAt(0) == 9 || String(trunk[0]).charCodeAt(0) == 32)
							trunk[0] = trunk[0].substring(1, trunk[0].length);
						
						if (j == 0) {
							_lastMtlID = trunk.join("");
							_lastMtlID = (_lastMtlID == "")? "def000" : _lastMtlID;
							
						} else {
							
							switch (trunk[0]) {
								
								case "Ka":
									if (trunk[1] && !isNaN(Number(trunk[1])) && trunk[2] && !isNaN(Number(trunk[2])) && trunk[3] && !isNaN(Number(trunk[3])))
										ambientColor = trunk[1]*255 << 16 | trunk[2]*255 << 8 | trunk[3]*255;
									break;
								
								case "Ks":
									if (trunk[1] && !isNaN(Number(trunk[1])) && trunk[2] && !isNaN(Number(trunk[2])) && trunk[3] && !isNaN(Number(trunk[3]))) {
										specularColor = trunk[1]*255 << 16 | trunk[2]*255 << 8 | trunk[3]*255;
										useSpecular = true;
									}
									break;
								
								case "Ns":
									if (trunk[1] && !isNaN(Number(trunk[1])))
										specular = Number(trunk[1])*0.001;
									if (specular == 0)
										useSpecular = false;
									break;
								
								case "Kd":
									if (trunk[1] && !isNaN(Number(trunk[1])) && trunk[2] && !isNaN(Number(trunk[2])) && trunk[3] && !isNaN(Number(trunk[3]))) {
										diffuseColor = trunk[1]*255 << 16 | trunk[2]*255 << 8 | trunk[3]*255;
										useColor = true;
									}
									break;
								
								case "tr":
								case "d":
									if (trunk[1] && !isNaN(Number(trunk[1])))
										alpha = Number(trunk[1]);
									break;
								
								case "map_Kd":
									mapkd = parseMapKdString(trunk);
									mapkd = mapkd.replace(/\\/g, "/");
							}
						}
					}
				}
				
				if (mapkd != "") {
					
					if (useSpecular) {
						
					}
					
				} else if (useColor && !isNaN(diffuseColor)) {
					
					if (alpha == 0)
						trace("Warning: an alpha value of 0 was found in mtl color tag (Tr or d) ref:" + _lastMtlID + ", mesh(es) using it will be invisible!");
				}
			}
			
			_mtlLibLoaded = true;
		}
		
		private function parseMapKdString(trunk:Array):String
		{
			var url:String = "";
			var i:int;
			var breakflag:Boolean;
			
			for (i = 1; i < trunk.length; ) {
				switch (trunk[i]) {
					case "-blendu":
					case "-blendv":
					case "-cc":
					case "-clamp":
					case "-texres":
						i += 2; //Skip ahead 1 attribute
						break;
					case "-mm":
						i += 3; //Skip ahead 2 attributes
						break;
					case "-o":
					case "-s":
					case "-t":
						i += 4; //Skip ahead 3 attributes
						continue;
					default:
						breakflag = true;
						break;
				}
				
				if (breakflag)
					break;
			}
			
			//Reconstruct URL/filename
			for (i; i < trunk.length; i++) {
				url += trunk[i];
				url += " ";
			}
			
			//Remove the extraneous space and/or newline from the right side
			url = url.replace(/\s+$/, "");
			
			return url;
		}

		
	}
}

class ObjectGroup
{
	public var name:String;
	public var groups:Vector.<Group> = new Vector.<Group>();
	
	public function ObjectGroup()
	{
	}
}

class Group
{
	public var name:String;
	public var materialID:String;
	public var materialGroups:Vector.<MaterialGroup> = new Vector.<MaterialGroup>();
	
	public function Group()
	{
	}
}

class MaterialGroup
{
	public var url:String;
	public var faces:Vector.<FaceData> = new Vector.<FaceData>();
	
	public function MaterialGroup()
	{
	}
}

class Vertex{
	public var x:Number = 0;
	public var y:Number = 0;
	public var z:Number = 0;
	public var index:uint
	public function Vertex(_x:Number = 0,_y:Number = 0,_z:Number = 0,_index:uint = 0):void{
		x = _x;y=_y;z=_z;index=_index;
	}
	
}

class UV{
	
	public var u:Number = 0;
	public var v:Number = 0;
	public function UV(_u:Number = 0,_v:Number = 0):void{
		u = _u;v = _v;
	}
}

class FaceData
{
	public var vertexIndices:Vector.<uint> = new Vector.<uint>();
	public var uvIndices:Vector.<uint> = new Vector.<uint>();
	public var normalIndices:Vector.<uint> = new Vector.<uint>();
	public var indexIds:Vector.<String> = new Vector.<String>(); // used for real index lookups
	
	public function FaceData()
	{
	}
}
